#ifndef SIMPLE_COMPRESS_ITERATOR_HPP
#define SIMPLE_COMPRESS_ITERATOR_HPP

#include <type_traits> // std::enable_if_t std::is_same_v std::is_unsigned_v
#include <iterator> // std::output_iterator_tag std::iterator_traits
#include <limits> // std::numeric_limits
#include <cstddef> // std::size_t
#include <utility> // std::forward
#include <algorithm> // std::min

#include "bits.hpp" // bit_count bit_offset get_bits


namespace simple::compress
{

	template <typename It, typename Value = typename std::iterator_traits<It>::value_type,
		std::enable_if_t<std::is_unsigned_v<std::underlying_type_t<Value>>>* = nullptr>
	struct out_bits
	{
		using value_type = Value;
		using difference_type = void;
		using pointer = void;
		using reference = void;
		using iterator_category = std::output_iterator_tag;

		It out;
		value_type value;
		std::size_t bit_index;

		template <typename I,
			std::enable_if_t<std::is_same_v<I,It>>* = nullptr>
		explicit out_bits(I&& out) :
			out(std::forward<It>(out)),
			value{}, bit_index{0}
		{}

		out_bits(out_bits&) = delete;
		out_bits(out_bits&& other) : out(other.out), value(other.value), bit_index(other.bit_index)
		{ other.bit_index = 0; }
		out_bits& operator=(out_bits&) = delete;
		out_bits& operator=(out_bits&& other)
		{
			std::swap(out, other.out);
			std::swap(value, other.value);
			std::swap(bit_index, other.bit_index);
			return *this;
		}

		out_bits& operator++() { return *this; }
		// move only can't have postincrement

		class proxy
		{
			It& out;
			Value& value;
			std::size_t& bit_index;

			proxy(It& out, Value& value, std::size_t& bit_index)
				: out(out), value(value), bit_index(bit_index) {}
			friend struct out_bits;
			public:
			proxy() = delete;
			proxy(const proxy&) = delete;
			proxy(const proxy&&) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(const proxy&&) = delete;

			template <typename T>
			const proxy& operator=(T&& x) const
			{
				auto count = bit_count(x);
				auto offset = bit_offset(x);
				auto bits = get_bits(x);

				constexpr auto type_width = std::numeric_limits<decltype(bits)>::digits;
				constexpr auto out_width = std::numeric_limits<std::underlying_type_t<Value>>::digits;

				if constexpr (type_width >= out_width)
				{
					constexpr auto type_shift = type_width - out_width;
					std::size_t i = 0;
					while(i != count)
					{
						value |= static_cast<Value>(bits
							<< (offset+i)
							>> (type_shift + bit_index)
						);
						using std::min;
						auto written = min(count-i, out_width - bit_index);
						i += written;
						bit_index += written;
						if(bit_index == out_width)
						{
							// TODO: out_end
							*out = value;
							++out;
							value = Value{};
							bit_index = 0;
						}
					}
				}
				else
				{
					// TODO this is actually the easier case
					static_assert(type_width < out_width, "not implemented");
				}



				return *this;
			}

		};

		const proxy operator*()
		{
			return {out, value, bit_index};
		}

		~out_bits()
		{
			if(bit_index != 0)
				*out = value;
		}
	};

	template <typename It> out_bits(It&&) -> out_bits<It>;

} // namespace simple::compress

#endif /* end of include guard */
